home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1994 November: Tool Chest / Dev.CD Nov 94.toast / Tool Chest / Development Tools & Languages / Dylan Related / Mindy-1.1 (sources only) / mindy-1.1 / libraries / dylan / table.dylan < prev    next >
Encoding:
Text File  |  1994-06-28  |  38.7 KB  |  1,181 lines  |  [TEXT/ttxt]

  1. module:        Dylan
  2. Author:        Nick Kramer (nkramer@cs.cmu.edu)
  3. rcs-header: $Header: table.dylan,v 1.7 94/06/27 17:10:38 wlott Exp $
  4.  
  5. //======================================================================
  6. //
  7. // Copyright (c) 1994  Carnegie Mellon University
  8. // All rights reserved.
  9. // 
  10. // Use and copying of this software and preparation of derivative
  11. // works based on this software are permitted, including commercial
  12. // use, provided that the following conditions are observed:
  13. // 
  14. // 1. This copyright notice must be retained in full on any copies
  15. //    and on appropriate parts of any derivative works.
  16. // 2. Documentation (paper or online) accompanying any system that
  17. //    incorporates this software, or any part of it, must acknowledge
  18. //    the contribution of the Gwydion Project at Carnegie Mellon
  19. //    University.
  20. // 
  21. // This software is made available "as is".  Neither the authors nor
  22. // Carnegie Mellon University make any warranty about the software,
  23. // its performance, or its conformity to any specification.
  24. // 
  25. // Bug reports, questions, comments, and suggestions should be sent by
  26. // E-mail to the Internet address "gwydion-bugs@cs.cmu.edu".
  27. //
  28. //======================================================================
  29.  
  30. /* -------------------------------------------------------------------
  31.  *
  32.  *  Implements <table>, <object-table>, <equal-table>, and <value-table>.
  33.  *
  34.  * ------------------------------------------------------------------- */
  35.  
  36. // Author's note: "ht" is my abbreviation for "hashtable", and is used
  37. // as a parameter quite frequently.
  38.  
  39. // <object-table>s are as defined in the book, operating on pointers and
  40. // using == as a comparator.
  41.  
  42. // <equal-table>s use = as a key test, but since = uses == as a
  43. // default method, <equal-table>s also have to worry about garbage
  44. // collection.
  45.  
  46. // <value-table>s are an abstract class who's hash function never
  47. // involves addresses (ie, always returns $permanent-hash-state). The
  48. // user defines a subclass of <value-table> and writes a method for
  49. // table-protocol. This will probably involve writing a new hash
  50. // function to be used on the hash keys. *Make sure this function does
  51. // not call object-hash*.
  52.  
  53. // For a more in depth explanation, see mindy.doc
  54.  
  55. /* -------------------------------------------------------------------
  56.  * Mindy-specific code
  57.  * ------------------------------------------------------------------- */
  58.  
  59. // merge-hash-codes is predefined in Mindy. However, at present
  60. // merge-hash-states is not. This calls merge-hash-codes and throws
  61. // away information about the hash ids.
  62.  
  63. define method merge-hash-states (state1 :: <object>, state2 :: <object>) 
  64.           => merged :: <object>;
  65.   let (junk, new-state) = merge-hash-codes (0, state1, 0, state2);
  66.  
  67.   new-state;
  68. end method merge-hash-states;
  69.  
  70. /* -------------------------------------------------------------------
  71.  * Stuff that Mindy takes care of, but other implementations might not:
  72.  * ------------------------------------------------------------------- */
  73.  
  74. // Also be sure to verify that equal-hash and value-hash work as
  75. // advertised. They depend on object-hash (which is always defined,
  76. // but might not behave as Mindy's does) and float-hash (which is
  77. // implemented in Mindy but not standard).
  78.  
  79. // define constant $permanent-hash-state = #f;
  80. //
  81. // // Define no-default if it isn't already defined somewhere else.
  82. // define constant no-default = "no-default";
  83. //
  84. // define constant magic-hash-constant = #x3fffffff;
  85. //         // And'ed with hash id's to keep the size under control 
  86. //         // when <integer> is <extended-integer>. This constant
  87. //         // should be as many consecutive 1 bits as will fit into a positive
  88. //         // <fixed-integer>.
  89. // 
  90. // define constant shift-dist          = 15;
  91. //         // This should be one half the size of an integer (in bits)
  92. //         // for reason that xor'ing the right shifted with the left
  93. //         // left shifted hash value is less sensical if shift-dist
  94. //         // is not 1/2 int size
  95. // 
  96. // define constant $permanent-hash-state = #f;
  97. // 
  98. // /* ---------------- */
  99. // 
  100. // define method merge-hash-ids (id1 :: <integer>, id2 :: <integer>,
  101. //                   #key ordered: ordered = #f )
  102. //                     => hash-id :: <integer>;
  103. // 
  104. //   if (ordered)
  105. //     logand (magic-hash-constant,
  106. //         logxor (logxor (ash (id1, shift-dist),
  107. //                 ash (id1, -shift-dist)),
  108. //             id2));
  109. //   else
  110. //     logand (magic-hash-constant, logxor (id1, id2));
  111. //   end if;
  112. // end method merge-hash-ids;
  113. // 
  114. // /* ---------------- */
  115. // 
  116. // define method merge-hash-states (state1, state2)
  117. //   if (state1 ~= $permanent-hash-state)
  118. //          if (state2 ~= $permanent-hash-state)
  119. //        min (state1, state2);
  120. //      else
  121. //        state1;
  122. //      end if;
  123. //   else
  124. //          state2;
  125. //   end if;
  126. // end method merge-hash-states;
  127. // 
  128. // /* ---------------- */
  129. // 
  130. // define method merge-hash-codes (id1 :: <integer>, state1,
  131. //                 id2 :: <integer>, state2,
  132. //                 #key ordered: ordered = #f )
  133. // 
  134. //   values ( merge-hash-ids (id1, id2, ordered: ordered),
  135. //        merge-hash-states (state1, state2)
  136. //      );
  137. // end method merge-hash-codes;
  138.  
  139. /* -------------------------------------------------------------------
  140.  * Portable implementation
  141.  * ------------------------------------------------------------------- */
  142.  
  143. define constant default-starting-table-size :: <integer> =  5;
  144. define constant default-expand-when         :: <integer> = 200;
  145. define constant default-expand-to           :: <integer> = 300;
  146. define constant default-shrink-when         :: <integer> = 10;
  147. define constant default-shrink-to           :: <integer> = 100;
  148.  
  149. /* These numbers are expressed as percentages. 200 for expand-when means
  150.  * when there are two objects for every bucket, the hash table will grow
  151.  * to expand-to % of the original size. (Make sure how-much is greater than
  152.  * 100%, or you won't get what you want)
  153.  * Default-shrink-when and -to are handled similarly. Shrink conditions
  154.  * are checked only when someone removes an element, and expand only
  155.  * when someone adds an element. Be careful not to set shrink-when too
  156.  * high, because if you do the table could shrink immediately after it
  157.  * expands.
  158.  */
  159.  
  160. /* ---------------- */
  161.  
  162. define class <bucket-entry> (<object>)
  163.   slot key-slot                  , required-init-keyword: key:               ;
  164.   slot hash-id-slot  :: <integer>, required-init-keyword: hash-id:           ;
  165.   slot hash-state-slot           , required-init-keyword: hash-state:        ;
  166.   slot item-slot                 , required-init-keyword: item:              ;
  167. end class <bucket-entry>;
  168.  
  169. /* ---------------- */
  170.  
  171. define class <table> (<mutable-explicit-key-collection>,
  172.                <stretchy-collection>)
  173.   slot item-count-slot         :: <integer>;
  174.            // Number of keys
  175.   slot bucket-array-slot       :: <vector>;
  176.   slot bucket-count-slot       :: <integer>;
  177.            // size of bucket-array
  178.   slot bucket-states-slot      :: <vector>;
  179.   slot expand-when-slot        :: <integer>;
  180.   slot expand-to-slot          :: <integer>;
  181.   slot shrink-when-slot        :: <integer>;
  182.   slot shrink-to-slot          :: <integer>;
  183.   slot merged-hash-state-slot  :: <object>;
  184. end class <table>;
  185.       
  186. /* ---------------- */
  187.  
  188. // Uses == (aka id?) as key comparison
  189.  
  190. define class <object-table> (<table>)
  191. end class <object-table>;
  192.  
  193. /* ---------------- */
  194.  
  195. // Uses = as key comparison
  196.  
  197. define class <equal-table> (<table>)
  198. end class <equal-table>;
  199.  
  200. /* ---------------- */
  201.  
  202. define abstract class <value-table> (<table>)
  203. end class <value-table>;
  204.  
  205. /* ---------------- */
  206.  
  207. define method make-bucket-entry (key, hash-id :: <integer>, hash-state, item)
  208.           => entry :: <bucket-entry>;
  209.   make (<bucket-entry>,   
  210.     key:        key, 
  211.     hash-id:    hash-id, 
  212.     hash-state: hash-state,
  213.     item:       item);
  214. end method make-bucket-entry;
  215.  
  216. /* ---------------- */
  217.  
  218. define method make (c :: singleton (<table>), #rest key-value-pairs,
  219.             #all-keys)  =>  table :: <object-table>;
  220.   apply (make, <object-table>, key-value-pairs);
  221. end method make;
  222.  
  223. /* ---------------- */
  224.  
  225. define method initialize (ht :: <table>,
  226.               #next next-method,
  227.               #key size: size       = default-starting-table-size,
  228.               buckets: numbuckets   = default-starting-table-size,
  229.               expand-when: expand-when = default-expand-when,
  230.               expand-to:   expand-to   = default-expand-to,
  231.               shrink-when: shrink-when = default-shrink-when,
  232.               shrink-to:   shrink-to   = default-shrink-to);
  233.  
  234.   ht.bucket-array-slot    := make (<simple-object-vector>, 
  235.                    size: numbuckets,
  236.                    fill: #() );     // filled with empty lists
  237.  
  238.   ht.bucket-states-slot   := make (<simple-object-vector>,
  239.                    size: numbuckets,
  240.                    fill: $permanent-hash-state);
  241.  
  242.   ht.item-count-slot        := 0;
  243.   ht.bucket-count-slot      := numbuckets;
  244.   ht.expand-when-slot       := expand-when;
  245.   ht.expand-to-slot         := expand-to;
  246.   ht.shrink-when-slot       := shrink-when;
  247.   ht.shrink-to-slot         := shrink-to;
  248.   ht.merged-hash-state-slot := $permanent-hash-state;
  249.  
  250.   next-method ();
  251. end method initialize;
  252.  
  253. /* ---------------- */
  254.  
  255. define method key-test (ht :: <table>) => test :: <function>;
  256.   let test = table-protocol(ht);    // drop the second return value
  257.   test;
  258. end method key-test;
  259.  
  260. /* ---------------- */
  261.  
  262. // equal-hash is used in the table-protocol as the hash-function 
  263. // for equal tables. Calling convention is similar to object-hash.
  264.  
  265. // The default method for objects that don't have any 
  266. // better methods defined. (We can't call object-hash, so what can we do?)
  267.  
  268. define method equal-hash (key :: <object>) 
  269.           => (id :: <integer>, state :: <object>);
  270.   values (42, $permanent-hash-state);
  271. end method equal-hash;
  272.  
  273.  
  274. // Call object-hash for characters, integers, symbols, classes,
  275. // functions, and conditions.
  276.  
  277. define method equal-hash (key :: <character>)
  278.           => (id :: <integer>, state :: <object>);
  279.   object-hash (key);
  280. end method equal-hash;
  281.  
  282.  
  283. define method equal-hash (key :: <integer>)
  284.           => (id :: <integer>, state :: <object>);
  285.   object-hash (key);
  286. end method equal-hash;
  287.  
  288.  
  289. define method equal-hash (key :: <float>)
  290.           => (id :: <integer>, state :: <object>);
  291.   float-hash(key);
  292. end method equal-hash;
  293.  
  294.  
  295. define method equal-hash (key :: <symbol>)
  296.           => (id :: <integer>, state :: <object>);
  297.   object-hash (key);
  298. end method equal-hash;
  299.  
  300.  
  301. define method equal-hash (key :: <class>)
  302.           => (id :: <integer>, state :: <object>);
  303.   object-hash (key);
  304. end method equal-hash;
  305.  
  306.  
  307. define method equal-hash (key :: <function>)
  308.           => (id :: <integer>, state :: <object>);
  309.   object-hash (key);
  310. end method equal-hash;
  311.  
  312.  
  313. define method equal-hash (key :: <type>)
  314.           => (id :: <integer>, state :: <object>);
  315.   object-hash (key);
  316. end method equal-hash;
  317.  
  318.  
  319. define method equal-hash (key :: singleton (#f))
  320.           => (id :: <integer>, state :: <object>);
  321.   object-hash (key);
  322. end method equal-hash;
  323.  
  324.  
  325. define method equal-hash (key :: singleton (#t))
  326.           => (id :: <integer>, state :: <object>);
  327.   object-hash (key);
  328. end method equal-hash;
  329.  
  330.  
  331. define method equal-hash (key :: <condition>)
  332.           => (id :: <integer>, state :: <object>);
  333.   object-hash (key);
  334. end method equal-hash;
  335.  
  336.  
  337. define method equal-hash (col :: <collection>)
  338.           => (id :: <integer>, state :: <object>);
  339.   collection-hash(col, equal-hash, equal-hash);
  340. end method equal-hash;
  341.  
  342. /* ---------------- */
  343.  
  344. // Object-hash returns $permanent-hash-state for <fix-num>s, the only
  345. // type of integer Mindy currently has. (Yes, ignore the "don't call
  346. // object-hash" warning at the beginning of this file. Trust me, this
  347. // works in *Mindy*) object-hash in Mindy does not return
  348. // $permanent-hash-state for anything else.
  349.  
  350. define method value-hash (key :: <integer>)
  351.           => (id :: <integer>, state :: <object>);
  352.   object-hash (key);
  353. end method value-hash;
  354.  
  355.  
  356. define method value-hash (key :: <float>)
  357.           => (id :: <integer>, state :: <object>);
  358.   float-hash(key);
  359. end method value-hash;
  360.  
  361.  
  362. define method value-hash (key :: <character>)
  363.           => (id :: <integer>, state :: <object>);
  364.   value-hash(as(<integer>, key));
  365. end method value-hash;
  366.  
  367.  
  368. define method value-hash (key :: <symbol>)
  369.           => (id :: <integer>, state :: <object>);
  370.   string-hash(as(<string>, key));
  371. end method value-hash;
  372.  
  373.  
  374. define method value-hash (key :: singleton (#f))
  375.           => (id :: <integer>, state :: <object>);
  376.   values(0, $permanent-hash-state);
  377. end method value-hash;
  378.  
  379.  
  380. define method value-hash (key :: singleton (#t))
  381.           => (id :: <integer>, state :: <object>);
  382.   values(1, $permanent-hash-state);
  383. end method value-hash;
  384.  
  385. /* ---------------- */
  386.  
  387. // You can't write a more specific method on collections because 
  388. // any two collections with identical key/element pairs are equal. 
  389. // Because of this, you can't merge-hash-codes with ordered: #t, or
  390. // really anything else interesting. In partial compensation, this
  391. // method hashes the keys as well as the elements. (As long as you
  392. // always put the element before the key when you merge hash codes,
  393. // you *can* use ordered: #t for merging them)
  394.  
  395. define method collection-hash(col :: <collection>, key-hash :: <function>,
  396.                   element-hash :: <function>)
  397.           => (id :: <integer>, state :: <object>);
  398.   let (current-id, current-state) = values (0, $permanent-hash-state);
  399.  
  400.   for (elt keyed-by key in col)
  401.     let (elt-id, elt-state)           = element-hash (elt);
  402.     let (key-id, key-state)           = key-hash (key);
  403.  
  404.     let (captured-id1, captured-state1) = merge-hash-codes (elt-id, elt-state,
  405.                                 key-id, key-state,
  406.                                 ordered: #t);
  407.  
  408.     let (captured-id2, captured-state2) = merge-hash-codes (current-id, 
  409.                                 current-state, 
  410.                                 captured-id1,
  411.                                 captured-state1,
  412.                                 ordered: #f);
  413.  
  414.     current-id    := captured-id2;
  415.     current-state := captured-state2;
  416.   end for;
  417.  
  418.   values (current-id, current-state);
  419. end method collection-hash;
  420.  
  421. /* ---------------- */
  422.  
  423. // This is similar to an equal-hash, except that it hashes things with
  424. // ordered: #t and ignores the sequence keys. USE WITH CAUTION: This
  425. // isn't a proper equal-hash because two collections of different types
  426. // but identical key/element pairs won't generate the same hash id,
  427. // even though the two collections are =.
  428.  
  429. define method sequence-hash(seq :: <sequence>, element-hash :: <function>)
  430.           => (id :: <integer>, state :: <object>);
  431.   let (current-id, current-state) = values (0, $permanent-hash-state);
  432.  
  433.   for (elt in seq)
  434.     let (id, state) = element-hash (elt);
  435.  
  436.     let (captured-id, captured-state) = merge-hash-codes (current-id, 
  437.                               current-state, 
  438.                               id, state,
  439.                               ordered: #t);
  440.  
  441.     current-id    := captured-id;
  442.     current-state := captured-state;
  443.   end for;
  444.  
  445.   values (current-id, current-state);
  446. end method sequence-hash;
  447.  
  448. /* ---------------- */
  449.  
  450. // A convenient method for hashing strings. Calls sequence-hash 
  451. // and "does the right thing."
  452.  
  453. define method string-hash (s :: <string>)
  454.     => (id :: <integer>, state :: <object>);
  455.   sequence-hash(s, value-hash);
  456. end method string-hash;
  457.  
  458. /* ---------------- */
  459.  
  460. define method table-protocol(ht :: <object-table>) 
  461.          => (key-test :: <function>, key-hash :: <function>);
  462.   values(\==, object-hash);
  463. end method table-protocol;
  464.  
  465.  
  466. define method table-protocol(ht :: <equal-table>) 
  467.          => (key-test :: <function>, key-hash :: <function>);
  468.   values(\=, equal-hash);
  469. end method table-protocol;
  470.  
  471. /* ---------------- */
  472.  
  473. // Informally, two hash tables are = if they use the same key test,
  474. // have the same size, and all the elements in the first hash table
  475. // have matching elements in the second hash table.
  476.  
  477. define constant not-in-ht2 = "not-in-ht2";
  478.  
  479. define method \= (ht1 :: <table>, ht2 :: <table>);
  480.   let test1 = key-test (ht1);
  481.   let test2 = key-test (ht2);
  482.  
  483.   (test1 == test2) 
  484.     & size(ht1) = size(ht2) 
  485.     & block (return)
  486.     for (elt1 keyed-by key in ht1)
  487.       let elt2 = element (ht2, key, default: not-in-ht2);
  488.       if (elt2 == not-in-ht2 | ~test1 (elt1, elt2))
  489.         return(#f);
  490.       end if;
  491.     end for;
  492.       
  493.     #t;
  494.       end block;
  495. end method \=;
  496.  
  497. /* ---------------- */
  498.  
  499. define method find-elt (list :: <list>, test :: <function>,
  500.             #key default: default = #f )
  501.     // Returns the first element of the list that satisfies
  502.     // test.
  503.  
  504.   if ( empty? (list) )
  505.     default;
  506.   else
  507.     if ( test (head (list)) )
  508.       head (list);
  509.     else
  510.       find-elt ( tail (list), test, default: default);
  511.     end if;
  512.   end if;
  513. end method find-elt;
  514.  
  515. /* ---------------- */
  516.  
  517. // This function looks redundant at times, but it's necessary in order
  518. // to avoid race conditions with the garbage collector.
  519.  
  520. define method element (  ht :: <table>, key, 
  521.                  #key default: default = no-default )
  522.   until (state-valid? (ht.merged-hash-state-slot))
  523.     rehash (ht);
  524.   end until;
  525.  
  526.   let (key=, key-hash)      = table-protocol(ht);
  527.  
  528.   let (key-id, key-state)   = key-hash (key);
  529.   let bucket-index          = modulo (key-id, ht.bucket-count-slot);
  530.   let bucket                = ht.bucket-array-slot [bucket-index];
  531.     
  532.   let test = method (entry :: <bucket-entry>)
  533.            (entry.hash-id-slot = key-id)
  534.          & key= (entry.key-slot, key);
  535.          end method;
  536.  
  537.   let find-result = find-elt (bucket, test);
  538.   
  539.      // Check to see if there was a garbage collection in the middle
  540.      // of this method. If there was, start over.
  541.  
  542.   if (~ state-valid? (ht.merged-hash-state-slot)
  543.       | ~ state-valid? (key-state) )
  544.     element (ht, key, default: default);
  545.        
  546.     // Else, there was no garbage collection, and we're safe.
  547.   elseif ( find-result )
  548.     find-result.item-slot;
  549.   elseif (default == no-default)
  550.     error ("Element not found");
  551.   else 
  552.     default;
  553.   end if;
  554. end method element;
  555.  
  556.  
  557. // This is exactly the same code without the garbage collection stuff
  558.  
  559. define method element (  ht :: <value-table>, key, 
  560.                  #key default: default = no-default )
  561.  
  562.   let (key=, key-hash)      = table-protocol(ht);
  563.  
  564.   let key-id                = key-hash (key);
  565.   let bucket-index          = modulo (key-id, ht.bucket-count-slot);
  566.   let bucket                = ht.bucket-array-slot [bucket-index];
  567.     
  568.   let test = method (entry :: <bucket-entry>)
  569.            (entry.hash-id-slot = key-id)
  570.          & key= (entry.key-slot, key);
  571.          end method;
  572.  
  573.   let find-result = find-elt (bucket, test);
  574.   
  575.   if ( find-result )
  576.     find-result.item-slot;
  577.   elseif (default == no-default)
  578.     error ("Element not found");
  579.   else 
  580.     default;
  581.   end if;
  582. end method element;
  583.  
  584. /* ---------------- */
  585.  
  586. // This function looks redundant at times, but it's necessary in order
  587. // to avoid race conditions with the garbage collector.
  588.  
  589. define method element-setter (value, ht :: <table>, key)
  590.  
  591.   until (state-valid? (ht.merged-hash-state-slot))
  592.     rehash (ht);
  593.   end until;
  594.  
  595.   let (key=, key-hash)    = table-protocol(ht);
  596.  
  597.   let (key-id, key-state) = key-hash (key);
  598.   let bucket-index        = modulo (key-id, ht.bucket-count-slot);
  599.   
  600.   let test-method         = method (existing-item :: <bucket-entry>)
  601.                   (existing-item.hash-id-slot = key-id)
  602.                 & key=(existing-item.key-slot, key);
  603.                 end method;
  604.  
  605.   let bucket-entry        = find-elt (ht.bucket-array-slot [bucket-index],
  606.                       test-method);
  607.  
  608.      // Check to see if there was a garbage collection in the middle
  609.      // of this method. If there was, start over.
  610.  
  611.   if (~ state-valid? (ht.merged-hash-state-slot)
  612.       | ~ state-valid? (key-state) )
  613.     element-setter (value, ht, key);
  614.        
  615.              // Else, there was no garbage collection, and we're safe.
  616.              // (If there is a garbage collection between now and the
  617.              // the end of this method, it invalidates the states we're
  618.              // about to write, but we can just re-compute them on
  619.              // the next lookup)
  620.  
  621.   else
  622.  
  623.     if (bucket-entry = #f)             // If item didn't exist, add it
  624.       bucket-entry := make-bucket-entry (key, key-id, key-state, value);
  625.  
  626.       ht.bucket-array-slot [bucket-index] := 
  627.          pair (bucket-entry, ht.bucket-array-slot [bucket-index]);
  628.       ht.item-count-slot := ht.item-count-slot + 1;
  629.  
  630.       if (size (ht) * 100 > (ht.bucket-count-slot * ht.expand-when-slot))
  631.     resize-table (ht, truncate/ (size(ht) * ht.expand-to-slot, 100) + 1);
  632.       end if;
  633.     else     // Item WAS found
  634.       bucket-entry.key-slot        := key;
  635.       bucket-entry.hash-id-slot    := key-id;
  636.       bucket-entry.hash-state-slot := key-state;
  637.       bucket-entry.item-slot       := value;
  638.     end if;
  639.  
  640.           // Update bucket's merged-hash-state
  641.     ht.bucket-states-slot [bucket-index] := 
  642.              merge-hash-states (bucket-entry.hash-state-slot, 
  643.                     ht.bucket-states-slot [bucket-index]);
  644.  
  645.     // Update table's merged hash codes
  646.     ht.merged-hash-state-slot := 
  647.       merge-hash-states (bucket-entry.hash-state-slot, 
  648.              ht.merged-hash-state-slot);
  649.     value;
  650.   end if;
  651. end method element-setter;
  652.  
  653.  
  654. // This is exactly the same code without the garbage collection stuff
  655.  
  656. define method element-setter (value, ht :: <value-table>, key)
  657.   let (key=, key-hash)    = table-protocol(ht);
  658.  
  659.   let key-id              = key-hash (key);
  660.   let bucket-index        = modulo (key-id, ht.bucket-count-slot);
  661.   
  662.   let test-method         = method (existing-item :: <bucket-entry>)
  663.                   (existing-item.hash-id-slot = key-id)
  664.                 & key=(existing-item.key-slot, key);
  665.                 end method;
  666.  
  667.   let bucket-entry        = find-elt (ht.bucket-array-slot [bucket-index],
  668.                       test-method);
  669.  
  670.   if (bucket-entry = #f)             // If item didn't exist, add it
  671.     bucket-entry := make-bucket-entry (key, key-id,
  672.                        $permanent-hash-state, 
  673.                        value);
  674.     
  675.     ht.bucket-array-slot [bucket-index] := 
  676.            pair (bucket-entry, ht.bucket-array-slot [bucket-index]);
  677.     ht.item-count-slot := ht.item-count-slot + 1;
  678.  
  679.     if (size (ht) * 100 > (ht.bucket-count-slot * ht.expand-when-slot))
  680.       resize-table (ht, truncate/ (size(ht) * ht.expand-to-slot, 100) + 1);
  681.     end if;
  682.   else     // Item WAS found
  683.     bucket-entry.key-slot        := key;
  684.     bucket-entry.hash-id-slot    := key-id;
  685.     bucket-entry.item-slot       := value;
  686.   end if;
  687.  
  688.   value;
  689. end method element-setter;
  690.  
  691. /* ---------------- */
  692.  
  693. define method remove-key! (ht :: <table>, key) => new-ht :: <table>;
  694.  
  695.   until (state-valid? (ht.merged-hash-state-slot))
  696.     rehash (ht);
  697.   end until;
  698.  
  699.   let (key=, key-hash)      = table-protocol(ht);
  700.  
  701.   let (key-id, key-state)   = key-hash (key);
  702.   let bucket-index          = modulo (key-id, ht.bucket-count-slot);
  703.   let bucket                = ht.bucket-array-slot [bucket-index];
  704.  
  705.   let test = method (existing-item :: <bucket-entry>)
  706.            (existing-item.hash-id-slot = key-id)
  707.          & key= (existing-item.key-slot, key);
  708.          end method;
  709.  
  710.   let the-item = find-elt (bucket, test);
  711.  
  712.   if (~ state-valid? (ht.merged-hash-state-slot)
  713.       | ~ state-valid? (key-state) )
  714.     remove-key! (ht, key);    // If state not valid, goto beginning
  715.                   // for a rehash
  716.   else
  717.     if (the-item ~= #f)       // An item with that key was found
  718.     ht.item-count-slot := ht.item-count-slot - 1;
  719.  
  720.            // Between find-elt and remove!, this traverses the bucket
  721.            // twice. It could be improved, but one has to be careful 
  722.            // to avoid race conditions with the garbage collector.
  723.  
  724.     ht.bucket-array-slot [bucket-index] := remove! (bucket, the-item);
  725.  
  726.         if (size (ht) * 100 < (ht.bucket-count-slot * ht.shrink-when-slot))
  727.       resize-table (ht, truncate/ (size(ht) * ht.shrink-to-slot, 100) + 1);
  728.     end if;
  729.  
  730.       // We leave all the merged-states as is. rehash will take care of it
  731.       // if a remove-key! made the merged-state information overly cautious.
  732.  
  733.     end if; // had to remove something
  734.  
  735.     ht;
  736.   end if;   // states valid?
  737. end method remove-key!;
  738.  
  739.  
  740. // This is exactly the same code without the garbage collection stuff
  741.  
  742. define method remove-key! (ht :: <value-table>, key) => new-ht :: <table>;
  743.   let (key=, key-hash)      = table-protocol(ht);
  744.  
  745.   let key-id                = key-hash (key);
  746.   let bucket-index          = modulo (key-id, ht.bucket-count-slot);
  747.   let bucket                = ht.bucket-array-slot [bucket-index];
  748.  
  749.   let test = method (existing-item :: <bucket-entry>)
  750.            (existing-item.hash-id-slot = key-id)
  751.          & key= (existing-item.key-slot, key);
  752.          end method;
  753.  
  754.   let the-item = find-elt (bucket, test);
  755.  
  756.   if (the-item ~= #f)       // An item with that key was found
  757.     ht.item-count-slot := ht.item-count-slot - 1;
  758.  
  759.            // Between find-elt and remove!, this traverses the bucket
  760.            // twice. It could be improved.
  761.  
  762.     ht.bucket-array-slot [bucket-index] := remove! (bucket, the-item);
  763.  
  764.     if (size (ht) * 100 < (ht.bucket-count-slot * ht.shrink-when-slot))
  765.       resize-table (ht, truncate/ (size(ht) * ht.shrink-to-slot, 100) + 1);
  766.     end if;
  767.   end if; // had to remove something
  768.  
  769.   ht;
  770. end method remove-key!;
  771.  
  772. /* ---------------- */
  773.  
  774. // Takes a hashtable and mutates it so that it has a different number of
  775. // buckets.
  776.  
  777. define method resize-table (ht :: <table>, numbuckets :: <integer>);
  778.   let new-array = make (<simple-object-vector>, 
  779.             size: numbuckets,
  780.             fill: #()   );
  781.  
  782.   let new-state-array = make (<simple-object-vector>,
  783.                   size: numbuckets,
  784.                   fill: $permanent-hash-state   );
  785.  
  786.   for (bucket in ht.bucket-array-slot)
  787.     for (entry in bucket)
  788.       let index = modulo (entry.hash-id-slot, numbuckets);
  789.       new-array [index] := pair (entry, new-array [index]);
  790.       new-state-array [index] := merge-hash-states(new-state-array [index],
  791.                            entry.hash-state-slot);
  792.     end for;
  793.   end for;
  794.  
  795.   ht.bucket-array-slot  := new-array;
  796.   ht.bucket-states-slot := new-state-array;
  797.   ht.bucket-count-slot  := numbuckets;
  798. end method resize-table;
  799.  
  800.  
  801. // This version of resize-table doesn't bother updating any of the
  802. // merged state slots, arrays, etc.
  803.  
  804. define method resize-table (ht :: <value-table>, numbuckets :: <integer>)
  805.   let new-array = make (<simple-object-vector>, 
  806.             size: numbuckets,
  807.             fill: #()   );
  808.  
  809.   for (bucket in ht.bucket-array-slot)
  810.     for (entry in bucket)
  811.       let index = modulo (entry.hash-id-slot, numbuckets);
  812.       new-array [index] := pair (entry, new-array [index]);
  813.     end for;
  814.   end for;
  815.  
  816.   ht.bucket-array-slot := new-array;
  817.   ht.bucket-count-slot := numbuckets;
  818. end method resize-table;
  819.  
  820. /* ---------------- */
  821.  
  822. // Rehash does its best to bring a table up to date so that all the
  823. // hash-id's in the table are valid. Rehash makes no guarentees about
  824. // its success, however, so one should call it inside an until loop
  825. // to make sure it keeps trying until it succeeds.
  826.  
  827. // Rehash wants to get the merged-hash-states to be as accurate as 
  828. // possible without sacraficing too much performance. This might be a
  829. // good function to tune.
  830.  
  831. define method rehash (ht :: <table>) => rehashed-ht :: <table>;
  832.   let (key=, key-hash)  =  table-protocol(ht);
  833.  
  834.   for (i from 0 below ht.bucket-count-slot)
  835.  
  836.     if (~ state-valid? (ht.bucket-states-slot [i]))     // rehash bucket
  837.       ht.bucket-states-slot [i] := $permanent-hash-state;
  838.  
  839.       let bucket    = ht.bucket-array-slot [i];
  840.       let prev      = #f;
  841.       let remaining = bucket;
  842.       
  843.              // This until is just like remove!, except that it
  844.          // rehashes things
  845.       until ( remaining == #() )
  846.     let bucket-entry = head (remaining);
  847.     let index        = i;
  848.  
  849.     if (state-valid? (bucket-entry.hash-state-slot))
  850.       prev        := remaining;
  851.       remaining   := tail (remaining);
  852.  
  853.     else  // state is invalid
  854.  
  855.       let (id, state) = key-hash (bucket-entry.key-slot);  
  856.       bucket-entry.hash-id-slot    := id;
  857.       bucket-entry.hash-state-slot := state;
  858.  
  859.       index := modulo (id, ht.bucket-count-slot);
  860.  
  861.       if (index = i)          // Keep its place in the list
  862.         prev := remaining;
  863.         remaining := tail (remaining);
  864.       else                    // Move entry
  865.         ht.bucket-array-slot [index] := 
  866.              pair (bucket-entry, ht.bucket-array-slot [index]);
  867.  
  868.               // Now remove it from old bucket
  869.         if (prev)
  870.           tail (prev) := tail (remaining);
  871.           remaining   := tail (remaining);
  872.         else
  873.           bucket      := tail (remaining);
  874.           prev        := #f;
  875.           remaining   := tail (remaining);
  876.         end if;  // If prev
  877.       end if;    // If index = i
  878.     end if;      // If state-valid? (bucket-entry)
  879.  
  880.     ht.bucket-array-slot [i] := bucket;
  881.     ht.bucket-states-slot [index] := 
  882.               merge-hash-states (bucket-entry.hash-state-slot,
  883.                      ht.bucket-states-slot [index]);
  884.  
  885.       end until;    // Finished traversing the bucket
  886.     end if;         // state-valid? (bucket-id-slots)
  887.   end for;
  888.  
  889.   ht.merged-hash-state-slot := reduce (merge-hash-states,
  890.                        $permanent-hash-state,
  891.                        ht.bucket-states-slot);
  892.   ht;
  893. end method rehash;
  894.  
  895. /* ---------------- */
  896.  
  897. define method size (ht :: <table>)
  898.   ht.item-count-slot;
  899. end method size;
  900.  
  901. /* ---------------- */
  902.  
  903. define method empty? (ht :: <table>)
  904.   ht.item-count-slot = 0;
  905. end method empty?;
  906.  
  907. /* ---------------- */
  908.  
  909. // Inherit mapping functions
  910.  
  911. /* -------------------------------------------------------------------
  912.  *                Iteration protocol stuff
  913.  * ------------------------------------------------------------------- */
  914.  
  915. // All these things are needed in the state, because many of the functions
  916. // get nothing but a hash table and a state.
  917.  
  918.  
  919. // This is the iteration state, not a hash-state
  920.  
  921. define class <ntable-state> (<object>)
  922.   slot elements-touched-slot,         init-keyword: elements-touched:      ;
  923.  
  924.   slot array-state-slot,              init-keyword: array-state:           ;
  925.   slot array-limit-slot,              init-keyword: array-limit:           ;
  926.   slot array-next-state-slot,         init-keyword: array-next-state:      ;
  927.   slot array-finished-state?-slot,    init-keyword: array-finished-state?: ;
  928.   slot array-current-key-slot,        init-keyword: array-current-key:     ;
  929.   slot array-current-element-slot,    init-keyword: array-current-element: ;
  930.   slot array-current-element-setter-slot,   
  931.                           init-keyword: array-current-element-setter:      ;
  932.   slot array-copy-state-slot,         init-keyword: array-copy-state:      ;
  933.  
  934.   slot bucket-state-slot,             init-keyword: bucket-state:          ;
  935.   slot bucket-limit-slot,             init-keyword: bucket-limit:          ;
  936.   slot bucket-next-state-slot,        init-keyword: bucket-next-state:     ;
  937.   slot bucket-finished-state?-slot,   init-keyword: bucket-finished-state?:;
  938.   slot bucket-current-key-slot,       init-keyword: bucket-current-key:    ;
  939.   slot bucket-current-element-slot,   init-keyword: bucket-current-element:;
  940.   slot bucket-current-element-setter-slot,       
  941.                            init-keyword: bucket-current-element-setter:    ;
  942.   slot bucket-copy-state-slot,        init-keyword: bucket-copy-state:     ;
  943.  
  944. end class <ntable-state>;
  945.  
  946. /* ---------------- */
  947.  
  948. define method finished-table-state? (ht :: <table>,
  949.                      state :: <ntable-state>,
  950.                      limit)
  951.   state.elements-touched-slot >= ht.item-count-slot;
  952. end method finished-table-state?;
  953.  
  954. /* ---------------- */
  955.  
  956. define method next-table-state (ht    :: <table>,
  957.                    state :: <ntable-state>) 
  958.                => new-state :: <ntable-state>;
  959.  
  960.   state.elements-touched-slot := state.elements-touched-slot + 1;
  961.  
  962.   if ( ~ finished-table-state? (ht, state, #f) )
  963.     let bucket = state.array-current-element-slot (ht.bucket-array-slot,
  964.                            state.array-state-slot);
  965.  
  966.     state.bucket-state-slot := 
  967.                 state.bucket-next-state-slot (bucket, state.bucket-state-slot);
  968.  
  969.     if (state.bucket-finished-state?-slot  (bucket,
  970.                         state.bucket-state-slot,
  971.                         state.bucket-limit-slot))
  972.       // Then move on to the next bucket
  973.       state.array-state-slot := 
  974.                        state.array-next-state-slot (ht.bucket-array-slot,
  975.                             state.array-state-slot);
  976.  
  977.       bucket := state.array-current-element-slot (ht.bucket-array-slot,
  978.                           state.array-state-slot);
  979.  
  980.       while (empty? (bucket))
  981.     state.array-state-slot := 
  982.                  state.array-next-state-slot (ht.bucket-array-slot,
  983.                           state.array-state-slot);
  984.  
  985.     bucket := state.array-current-element-slot (ht.bucket-array-slot,
  986.                             state.array-state-slot);
  987.       end while;
  988.  
  989.       let (next-bucket-initial-state,
  990.        next-bucket-limit,
  991.        next-bucket-next-state,
  992.        next-bucket-finished-state?,
  993.        next-bucket-current-key,
  994.        next-bucket-current-element,
  995.        next-bucket-current-element-setter,
  996.        next-bucket-copy-state) = 
  997.                       forward-iteration-protocol (bucket);
  998.  
  999.       state.bucket-state-slot                  := next-bucket-initial-state;
  1000.       state.bucket-limit-slot                  := next-bucket-limit;
  1001.       state.bucket-next-state-slot             := next-bucket-next-state;
  1002.       state.bucket-finished-state?-slot        := next-bucket-finished-state?;
  1003.       state.bucket-current-key-slot            := next-bucket-current-key;
  1004.       state.bucket-current-element-slot        := next-bucket-current-element;
  1005.       state.bucket-current-element-setter-slot :=
  1006.                             next-bucket-current-element-setter;
  1007.       state.bucket-copy-state-slot             := next-bucket-copy-state;    
  1008.     end if;           // End of things to do if bucket ran dry
  1009.   end if;             // End of more objects left in hash table?
  1010.  
  1011.   state;            // Return the new and improved state object
  1012. end method next-table-state;
  1013.  
  1014. /* ---------------- */
  1015.  
  1016. define method get-bucket-entry (ht :: <table>, state :: <ntable-state>)
  1017.                   => entry :: <bucket-entry>;
  1018.  
  1019.   let bucket = state.array-current-element-slot (ht.bucket-array-slot,
  1020.                          state.array-state-slot);
  1021.  
  1022.   state.bucket-current-element-slot (bucket, state.bucket-state-slot);
  1023. end method get-bucket-entry;
  1024.  
  1025. /* ---------------- */
  1026.  
  1027. define method current-table-key (ht :: <table>, state :: <ntable-state>)
  1028.   let bucket-entry = get-bucket-entry (ht, state);
  1029.  
  1030.   bucket-entry.key-slot;
  1031. end method current-table-key;
  1032.  
  1033. /* ---------------- */
  1034.  
  1035. define method current-table-element (ht :: <table>, state :: <ntable-state>)
  1036.   let bucket-entry = get-bucket-entry (ht, state);
  1037.  
  1038.   bucket-entry.item-slot;
  1039. end method current-table-element;
  1040.  
  1041. /* ---------------- */
  1042.  
  1043. define method current-table-element-setter (value,
  1044.                        ht    :: <table>,
  1045.                        state :: <ntable-state>)
  1046.          // This argument order isn't mentioned anywhere I can find,
  1047.          // but seems to be what is expected
  1048.  
  1049.   let bucket = state.array-current-element-slot (ht.bucket-array-slot,
  1050.                          state.array-state-slot);
  1051.   let new-bucket-entry = get-bucket-entry (ht, state);
  1052.  
  1053.   new-bucket-entry.item-slot := value;
  1054.   state.bucket-current-element-setter-slot (new-bucket-entry,
  1055.                         bucket,
  1056.                         state.bucket-state-slot);
  1057.   // Return value:
  1058.   value;
  1059. end method current-table-element-setter;
  1060.  
  1061. /* ---------------- */
  1062.  
  1063. define method copy-table-state (ht :: <table>, old-state :: <ntable-state>)
  1064.   let bucket    = old-state.array-current-element-slot (ht.bucket-array-slot,
  1065.                           old-state.array-state-slot);
  1066.   let new-state = make (<ntable-state>);
  1067.  
  1068.   new-state.array-state-slot  :=
  1069.     old-state.array-copy-state-slot (ht.bucket-array-slot,
  1070.                      old-state.array-state-slot);
  1071.  
  1072.   new-state.bucket-state-slot := 
  1073.     old-state.bucket-copy-state-slot (bucket, old-state.bucket-state-slot);
  1074.  
  1075.   new-state.array-next-state-slot      := old-state.array-next-state-slot;
  1076.   new-state.array-copy-state-slot      := old-state.array-copy-state-slot;
  1077.   new-state.array-current-key-slot     := old-state.array-current-key-slot;
  1078.   new-state.array-finished-state?-slot :=
  1079.              old-state.array-finished-state?-slot;
  1080.   new-state.array-current-element-slot := 
  1081.              old-state.array-current-element-slot;
  1082.   new-state.array-current-element-setter-slot :=
  1083.              old-state.array-current-element-setter-slot;
  1084.  
  1085.   new-state.bucket-next-state-slot      := old-state.bucket-next-state-slot;
  1086.   new-state.bucket-copy-state-slot      := old-state.bucket-copy-state-slot;
  1087.   new-state.bucket-current-key-slot     := old-state.bucket-current-key-slot;
  1088.   new-state.bucket-finished-state?-slot :=
  1089.              old-state.bucket-finished-state?-slot;
  1090.   new-state.bucket-current-element-slot := 
  1091.              old-state.bucket-current-element-slot;
  1092.   new-state.bucket-current-element-setter-slot :=
  1093.              old-state.bucket-current-element-setter-slot;
  1094.  
  1095.  
  1096.   new-state;
  1097. end method copy-table-state;
  1098.  
  1099. /* ---------------- */
  1100.  
  1101. define method make-table-state (ht :: <table>) 
  1102.                => table-state :: <ntable-state>;
  1103.  
  1104.   let (array-initial-state,
  1105.        array-limit,
  1106.        array-next-state,
  1107.        array-finished-state?,
  1108.        array-current-key,
  1109.        array-current-element,
  1110.        array-current-element-setter,
  1111.        array-copy-state) = forward-iteration-protocol (ht.bucket-array-slot);
  1112.  
  1113.   let init-state = make (<ntable-state>);
  1114.  
  1115.   init-state.elements-touched-slot :=             0;
  1116.  
  1117.   init-state.array-state-slot :=                  array-initial-state;
  1118.   init-state.array-limit-slot :=                  array-limit;
  1119.   init-state.array-next-state-slot :=             array-next-state;
  1120.   init-state.array-finished-state?-slot :=        array-finished-state?;
  1121.   init-state.array-current-key-slot :=            array-current-key;
  1122.   init-state.array-current-element-slot :=        array-current-element;
  1123.   init-state.array-current-element-setter-slot := array-current-element-setter;
  1124.   init-state.array-copy-state-slot :=             array-copy-state;
  1125.  
  1126.   if (ht.item-count-slot > 0)
  1127.     let bucket = init-state.array-current-element-slot (ht.bucket-array-slot,
  1128.                                   init-state.array-state-slot);
  1129.  
  1130.     while (empty?(bucket))             // Find first non-empty bucket
  1131.       init-state.array-state-slot := 
  1132.     init-state.array-next-state-slot (ht.bucket-array-slot,
  1133.                       init-state.array-state-slot);
  1134.       
  1135.       bucket := init-state.array-current-element-slot (ht.bucket-array-slot,
  1136.                         init-state.array-state-slot);
  1137.     end while;
  1138.  
  1139.           // In the case that the hash table is empty, the bucket states
  1140.           // are neither initialized nor needed.
  1141.  
  1142.     let (first-bucket-initial-state,
  1143.      first-bucket-limit,
  1144.      first-bucket-next-state,
  1145.      first-bucket-finished-state?,
  1146.      first-bucket-current-key,
  1147.      first-bucket-current-element,
  1148.      first-bucket-current-element-setter,
  1149.      first-bucket-copy-state) = 
  1150.                     forward-iteration-protocol (bucket);
  1151.  
  1152.     init-state.bucket-state-slot :=              first-bucket-initial-state;
  1153.     init-state.bucket-limit-slot :=              first-bucket-limit;
  1154.     init-state.bucket-next-state-slot :=         first-bucket-next-state;
  1155.     init-state.bucket-finished-state?-slot :=    first-bucket-finished-state?;
  1156.     init-state.bucket-current-key-slot :=        first-bucket-current-key;
  1157.     init-state.bucket-current-element-slot :=    first-bucket-current-element;
  1158.     init-state.bucket-current-element-setter-slot := 
  1159.                                          first-bucket-current-element-setter;
  1160.     init-state.bucket-copy-state-slot :=         first-bucket-copy-state;
  1161.   end if;
  1162.  
  1163.   init-state;                        // Return value
  1164. end method make-table-state;
  1165.  
  1166. /* ---------------- */
  1167.  
  1168. // The iteration protocol.
  1169.  
  1170. define method forward-iteration-protocol (ht :: <table>)
  1171.  
  1172.   values (make-table-state (ht),       // initial hash state
  1173.       #f,             // limit -- isn't actually used by finished-state?
  1174.       next-table-state,
  1175.       finished-table-state?,
  1176.       current-table-key,
  1177.       current-table-element,
  1178.       current-table-element-setter,
  1179.       copy-table-state);
  1180. end method forward-iteration-protocol;
  1181.